//
//  DGDownloadManager.m
//  DGDownloader
//
//  Created by Jinxiao on 4/15/16.
//  Copyright © 2016 debugeek. All rights reserved.
//

#import "DGDownloadManager.h"

#import "DGDownloadTask.h"

#import "SJURLSessionOperation.h"

#import <libkern/OSAtomic.h>

@interface DGDownloadManager ()
@property (readwrite, nonatomic, strong) NSMutableDictionary <NSString *, NSMutableArray *> *observers;
@property (readwrite, nonatomic, strong) NSOperationQueue *queue;
@property (readwrite, nonatomic, strong) NSMutableArray <DGDownloadTask *> *tasks;
@property (readwrite, nonatomic, assign) OSSpinLock lock;
@end

@implementation DGDownloadManager

+ (instancetype)sharedInstance {
    static id instance;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        instance = self.new;
    });

    return instance;
}

- (instancetype)init {
    self = [super init];

    _observers = [[NSMutableDictionary alloc] init];

    _queue = [[NSOperationQueue alloc] init];
    _queue.maxConcurrentOperationCount = 3;

    return self;
}

- (void)addDownloadObserver:(id<DGDownloadManagerDelegate>)observer forIdentifier:(NSString *)identifier {
    if(![observer conformsToProtocol:@protocol(DGDownloadManagerDelegate)] || !identifier) {
        return;
    }

    OSSpinLockLock(&_lock);

    NSMutableArray *observers = [_observers objectForKey:identifier];

    if(observers == nil) {
        observers = [[NSMutableArray alloc] init];
        [_observers setObject:observers forKey:identifier];
    }

    if(![observers containsObject:observer]) {
        [observers addObject:observer];
    }

    OSSpinLockUnlock(&_lock);
}

- (void)removeDownloadObserver:(id<DGDownloadManagerDelegate>)observer forIdentifier:(NSString *)identifier;
{
    if(!identifier) {
        return;
    }

    OSSpinLockLock(&_lock);

    NSMutableArray *observers = [_observers objectForKey:identifier];

    if(observers != nil) {
        [observers removeObject:observer];
    }

    if(observers.count == 0) {
        [_observers removeObjectForKey:identifier];
    }

    OSSpinLockUnlock(&_lock);
}

- (void)didStartDownloadTask:(DGDownloadTask *)downloadTask {
    downloadTask.lastError = nil;
    downloadTask.startDate = [NSDate date];
    downloadTask.finishDate = nil;

    NSMutableArray *observers = [_observers objectForKey:downloadTask.identifier];

    for(id<DGDownloadManagerDelegate> observer in observers) {
        if([observer respondsToSelector:@selector(downloadManager:didStartDownloadTask:)]) {
            [observer downloadManager:self didStartDownloadTask:downloadTask];
        }
    }

    OSSpinLockLock(&_lock);

    [_tasks addObject:downloadTask];

    OSSpinLockUnlock(&_lock);
}


- (void)didFinishDownloadTask:(DGDownloadTask *)downloadTask {
    downloadTask.finishDate = [NSDate date];

    !downloadTask.completion ? : downloadTask.completion(downloadTask.filePath, downloadTask.lastError);

    NSMutableArray *observers = [_observers objectForKey:downloadTask.identifier];

    for(id<DGDownloadManagerDelegate> observer in observers) {
        if([observer respondsToSelector:@selector(downloadManager:didFinishDownloadTask:)]) {
            [observer downloadManager:self didFinishDownloadTask:downloadTask];
        }
    }

    OSSpinLockLock(&_lock);

    [_tasks removeObject:downloadTask];

    OSSpinLockUnlock(&_lock);
}

- (void)didProcessDownloadTask:(DGDownloadTask *)downloadTask receivedBytes:(NSUInteger)receivedBytes totalBytes:(NSUInteger)totalBytes {
    double progress = totalBytes == 0 ? 0 : receivedBytes/totalBytes;

    !downloadTask.progressive ? : downloadTask.progressive(MAX(0, MIN(1, progress)));

    NSMutableArray *observers = [_observers objectForKey:downloadTask.identifier];

    for(id<DGDownloadManagerDelegate> observer in observers) {
        if([observer respondsToSelector:@selector(downloadManager:didProcessDownloadTask:receivedBytes:totalBytes:)]) {
            [observer downloadManager:self didProcessDownloadTask:downloadTask receivedBytes:receivedBytes totalBytes:totalBytes];
        }
    }
}

#pragma mark - Queueing

- (BOOL)enqueueDownloadTask:(DGDownloadTask *)downloadTask {
    if(downloadTask.identifier.length == 0) {
        return NO;
    }

    BOOL existing = NO;

    for(DGDownloadTask *task in _tasks) {
        if([task.identifier isEqualToString:downloadTask.identifier]) {
            existing = YES;
            break;
        }
    }

    if(existing) {
        return NO;
    }

    __weak typeof(self)weakSelf = self;

    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:downloadTask.sourceURL];
    SJURLSessionOperation *operation = [[SJURLSessionOperation alloc] initWithRequest:request targetLocation:[[NSURL alloc] initFileURLWithPath:downloadTask.filePath]];
    [operation setDownloadProgressBlock:^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
        [weakSelf didProcessDownloadTask:downloadTask receivedBytes:(NSUInteger)totalBytesWritten totalBytes:(NSUInteger)totalBytesExpectedToWrite];
    }];
    [operation setDownloadCompletionBlock:^(SJURLSessionOperation *_Nullable operation, NSError *_Nullable error, NSURL *_Nullable fileURL, NSURLResponse *_Nullable response) {
        if(error) {
            downloadTask.lastError = error;
            [weakSelf didFinishDownloadTask:downloadTask];
        } else {
            [weakSelf didFinishDownloadTask:downloadTask];
        }
    }];

    [_queue addOperation:operation];

    [weakSelf didStartDownloadTask:downloadTask];

    return YES;
}

@end
